package com.util.orm.dao.impl;

import com.util.common.Page;
import com.util.common.QueryList;
import com.util.common.QueryParam;
import com.util.orm.dao.Dao;
import com.util.orm.dao.DataAccessException;
import com.util.orm.dao.SqlMapStatementEnum;
import com.util.orm.dao.SqlSessionTemplateRouter;
import com.util.orm.util.BeanUtil;
import com.util.orm.util.ShardingKeyBuilder;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * mybatis 基础dao类 注意：子类一定要重写getIbatisSqlMapNamespace()
 * @param <E>
 * @param <PK>
 */
public  class BaseDao<E, PK extends Serializable> extends SqlSessionDaoSupport implements Dao<E, PK> {
	
	private static Logger log = LoggerFactory.getLogger(BaseDao.class);
	
	private ThreadLocal<Boolean> batchMode = new ThreadLocal<Boolean>();
	
	/**
	 * batch operations
	 */
	private SqlSessionTemplate batchSqlSessionTemplate;

	private SqlSessionTemplateRouter sqlSessionTemplateRouter;
	
	@Autowired(required = false)
	@Qualifier("sqlSessionTemplate")
	@Override
	public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
		super.setSqlSessionTemplate(sqlSessionTemplate);
	}

	@Autowired(required = false)
	public void setSqlSessionTemplateRouter(SqlSessionTemplateRouter sqlSessionTemplateRouter) {
		this.sqlSessionTemplateRouter = sqlSessionTemplateRouter;
		setSqlSessionTemplate(sqlSessionTemplateRouter.getSqlSessionTemplate(null));
	}

	public ShardingKeyBuilder getShardingKeyBuilder() {
		return null;
	}

	public SqlSession getSqlSession() {
		if( !isInBatch() ) {
			SqlSession sqlSession = super.getSqlSession();
			if (null != sqlSession) {
				return sqlSession;
			}
			return getSqlSession(null);
		}else {
			return batchSqlSessionTemplate;
		}
	}

	public SqlSession getSqlSession(Object param) {
		ShardingKeyBuilder builder = getShardingKeyBuilder();
		if (null == builder && null != param) {
			throw new RuntimeException("getSqlSession by key, but ShardingKeyBuilder not set");
		}
		String key = null;
		if (null != builder) {
			key = builder.getDBShardingKey(param);
		}
		if (isInBatch() && null != key) {
			throw new RuntimeException("getSqlSession by key does not support batch mode except default datasource");
		}
		if (null == sqlSessionTemplateRouter) {
			throw new RuntimeException("getSqlSession by key, but sqlSessionTemplateRouter is not set");
		}
		return sqlSessionTemplateRouter.getSqlSessionTemplate(key);
	}
	
	@Override
	public E getByPk(PK primaryKey) throws DataAccessException {
		String  sqlId0 = getFindByPrimaryKeyStatement();
		E object = getSqlSession().selectOne( sqlId0 , primaryKey);
		return object;
	}
	
	@Override
	public E selectOne(String sqlId, Object param) throws DataAccessException {
		String sqlId0 = buildFullSqlId( sqlId );
		E object = getSqlSession().selectOne(sqlId0, param);
		return object;
	}
	
	@Override
	public int deleteByPk(PK primaryKey) throws DataAccessException {
		String  sqlId0 = getDeleteStatement();
		return getSqlSession().delete( sqlId0 , primaryKey);
	}

	@Override
	public int insert(E entity) throws DataAccessException {
		prepareObjectBeforeSave(entity);
		int i = getSqlSession().insert(getInsertStatement(), entity);
		prepareObjectAfterSave(entity);
		return i;
	}

	@Override
	public int update(E entity) throws DataAccessException {
		prepareObjectBeforeUpdate(entity);
		int i = getSqlSession().update(getUpdateStatement(), entity);
		return i;

	}
	
	@Override
	public int updateByParam(String sqlId, Object param) {
		String sqlId0 = buildFullSqlId( sqlId );
		int i = getSqlSession().update(sqlId0, param);
		return i;
	}
	
	@Override
	public int update(String sqlId ,E entity) throws DataAccessException {
		String sqlId0 = buildFullSqlId( sqlId );
		prepareObjectBeforeUpdate(entity);
		int i = getSqlSession().update( sqlId0, entity);
		prepareObjectAfterUpdate(entity);
		return i;

	}

	/**
	 * select one record
	 * @see Dao#selectOne(Object)
	 */
	@Override
	public E selectOne(Object param) throws org.springframework.dao.DataAccessException {
		return this.selectOne( SqlMapStatementEnum.SELECT_ONE_OBJECT.getStatementId(), param);
	}

	@Override
	public List<E> selectList(Object param) {
		return this.selectList(  SqlMapStatementEnum.LIST_OBJECT.getStatementId() , param);
	}

	@Override
	public List<E> selectList(String sqlId, Object param) {
		String sqlId0 = buildFullSqlId( sqlId );
		return getSqlSession().selectList( sqlId0, param);
	}
	
	@Override
	public int delete(E entity) throws org.springframework.dao.DataAccessException {
		 return getSqlSession().delete( SqlMapStatementEnum.DELETE_OBJECT.getStatementId() , entity);
	}
	
	@Override
	public int delete(String sqlId, E entity) throws org.springframework.dao.DataAccessException {
		String sqlId0 = buildFullSqlId( sqlId );
		return getSqlSession().delete(sqlId0, entity);
	}
	
	/**
	 * 分页查询, 指定可分页查询条件
	 * @param queryParam
	 * @return
	 */
	public QueryList<E> selectPage(QueryParam queryParam) {
		return this.selectPage( SqlMapStatementEnum.LIST_PAGE_OBJECT.getStatementId() , queryParam, null);
	}
	
	
	/**
	 * 分页查询, 指定特定sqlId语句，指定可分页查询条件
	 * @param sqlId
	 * @param queryParam
	 * @param otherParam
	 * @return
	 */
	@Override
	public QueryList<E> selectPage(String sqlId, QueryParam queryParam, Object otherParam) {
		//组装参数，将两个参数组装为一个新的map参数，作为实际查询的map参数
		
		String sqlId0 = buildFullSqlId( sqlId );
		
		Map<String,Object> actualParam = new HashMap<>();
		
		if(queryParam.getPageSize() > queryParam.maxPageSize())
			queryParam.setPageSize(queryParam.maxPageSize());
		
		actualParam.put("autoCount", queryParam.autoCount());
		actualParam.put("offset", queryParam.getOffset());
		actualParam.put("pageSize", queryParam.getPageSize());
		actualParam.put("lastRows", queryParam.getOffset() + queryParam.getPageSize());
		
		Map<String,Object> newParamMap1 = queryParam!=null ? BeanUtil.bean2Map(queryParam): null;
		Map<String,Object> newParamMap2 = otherParam!=null ? BeanUtil.bean2Map(otherParam) : null;
		
		if(newParamMap1!=null) actualParam.putAll(newParamMap1);
		if(newParamMap2!=null) actualParam.putAll(newParamMap2);
		
		List<E> resultList = getSqlSession().selectList( sqlId0 , actualParam);
		Integer totalCount = 0;
		
		//如果 autoCount为true, otherFilters从 PageInteceptor中返回后，会自带 totalCount参数输出
		if (null != actualParam.get("totalCount")) {
			totalCount = (Integer) actualParam.get("totalCount");
		} else if (resultList.size() >= 0) {
			totalCount =  resultList.size();
		}

		QueryList<E> pageList = new QueryList<>();
		pageList.setPage(new Page(queryParam.getPageSize(),queryParam.getPage(),totalCount));
		pageList.setResultList(resultList);
		return pageList;
	}

	@Override
	public Long getCount(Object param) {
		return this.getCount( SqlMapStatementEnum.COUNT_OBJECT.getStatementId(), param);
	}

	@Override
	public Long getCount(String sqlId, Object param) {
		String sqlId0 = buildFullSqlId( sqlId );
		return (Long) getSqlSession().selectOne(sqlId0, param);
	}

	private String getFindByPrimaryKeyStatement() {
		return buildFullSqlId(  SqlMapStatementEnum.GET_BY_ID.getStatementId());
	}

	private String getDeleteStatement() {
		return buildFullSqlId(  SqlMapStatementEnum.DELETE_OBJECT.getStatementId());
	}

	private String getInsertStatement() {
		return buildFullSqlId(  SqlMapStatementEnum.INSERT_OBJECT.getStatementId());
	}

	private String getUpdateStatement() {
		return buildFullSqlId(  SqlMapStatementEnum.UPDATE_OBJECT.getStatementId());
	}

	private String getCountStatement() {
		return buildFullSqlId( SqlMapStatementEnum.COUNT_OBJECT.getStatementId());
	}
	
	private String getSelectOneStatement() {
		return buildFullSqlId( SqlMapStatementEnum.SELECT_ONE_OBJECT.getStatementId());
	}
	
	private String getListStatement() {
		return buildFullSqlId(SqlMapStatementEnum.LIST_OBJECT.getStatementId());
	}

	private String getListStatementForPage() {
		return  buildFullSqlId(SqlMapStatementEnum.LIST_PAGE_OBJECT.getStatementId());
	}

	private String buildFullSqlId(String sqlId) {
		String sqlId0 = sqlId;
		if( !sqlId.startsWith(".")) {
			sqlId0 = "." + sqlId;
		}
		return  getIbatisSqlMapNamespace() + sqlId0;
	}
	
	/**
	 * 得到ibatis的Namespace 子类一定要重写此类
	 * 
	 * @return
	 */
	public String getIbatisSqlMapNamespace() {
		return this.getClass().getInterfaces()[0].getName();

	}

	/**
	 * 用于子类覆盖,在update之后调用
	 * 
	 * @param o
	 */

	protected void prepareObjectAfterUpdate(E o) {
	}
	/**
	 * 用于子类覆盖,在update之前调用
	 * 
	 * @param o
	 */
	protected void prepareObjectBeforeUpdate(E o) {
	}
	/**
	 * 用于子类覆盖,在insert之前调用
	 * 
	 * @param o
	 */
	protected void prepareObjectBeforeSave(E o) {
	}
	/**
	 * 用于子类覆盖,在insert之后调用
	 * 
	 * @param o
	 */
	protected void prepareObjectAfterSave(E o) {
	}

	@Autowired(required = false)
	@Qualifier("batchSqlSessionTemplate")
	public void setBatchSqlSessionTemplate(SqlSessionTemplate batchSqlSessionTemplate) {
		this.batchSqlSessionTemplate = batchSqlSessionTemplate;
	}
	
	protected boolean isInBatch() {
		if( batchMode.get() == null ||  batchMode.get()==false) {
			return false;
		}else {
			return batchMode.get();
		}
	}
	
	@Override
	public void startBatch() throws DataAccessException {
		batchMode.set(true);
	}
	
	@Override
	public int flushBatch() throws org.springframework.dao.DataAccessException {
		return batchSqlSessionTemplate.flushStatements().size();
	}
	
	
	@Override
	public void executeBatch() throws DataAccessException {
		executeBatch(false);
	}
	
	@Override
	public void cancelBatch() throws DataAccessException {
		cancelBatch( false);
	}
	
	/**
	 * 提交批量操作。如果commitTx为true，也提交事务
	 */
	@Override
	public void executeBatch(boolean commitTx)	throws DataAccessException {
		if( ! isInBatch() ) 
			throw new DataAccessException("Not in batch mode, but executeBatch() is called!");
		try{
			batchSqlSessionTemplate.commit(commitTx);
			if(log.isInfoEnabled())
				log.info("Batch operations ::: executeBatch(" + commitTx +")---" + this.getClass().getSimpleName());
		}finally {
			batchMode.set(false);
		}
	}
	
	@Override
	public void cancelBatch(boolean commitTx)	throws DataAccessException {
		if( ! isInBatch() ) 
			throw new DataAccessException("Not in batch mode, but cancelBatch() is called!");
		try{
			batchSqlSessionTemplate.rollback(commitTx);
			if(log.isInfoEnabled())
				log.info("Batch operations ::: cancelBatch (" + commitTx +")---" + this.getClass().getSimpleName());
		}finally {
			batchMode.set(false);
		}
	}
}
